package org.shadow.core.key.database;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.shadow.core.key.AbstractKeyGenerator;
import org.shadow.core.key.exception.InvalidSequenceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>
 * DatabaseKeyGenerator 从数据库读取当前主键类型的键值，并尝试增加步长，更新到数据库，生成主键
 * </p>
 * 
 * @author zhouw
 * @version 0.1
 * @since 2016.12.02
 */
public class DatabaseKeyGenerator extends AbstractKeyGenerator {

	private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseKeyGenerator.class);

	private DatabaseSequenceTable seqTable;

	private DataSource dataSource;

	/**
	 * 重试次数 对采用悲观锁的实现无效
	 **/
	private int retryCount = 50;

	@Override
	protected int getCurrentSequence(int step)
			throws InvalidSequenceException, IllegalArgumentException, IllegalStateException {
		if (this.dataSource == null || this.seqTable == null) {
			throw new IllegalArgumentException("Datasource or SeqTable is null");
		}
		
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			conn = dataSource.getConnection();
			boolean defaultAutoCommit = conn.getAutoCommit();
			conn.setAutoCommit(false);
			int updateCount = 0;
			int currentSequence = 0;
			int defaultTranscationIoslation = conn.getTransactionIsolation();
			conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
			
			while (updateCount == 0 && this.retryCount-- > 0) {
				pstmt = conn.prepareStatement(this.seqTable.getSelectSql());
				pstmt.setString(1, this.getKeyType());
				rs = pstmt.executeQuery();
				if (!rs.next()) {
					throw new IllegalStateException("key type[+" + this.getKeyType() + " ] is uninitialized");
				} 
				int dbSequence = rs.getInt(this.seqTable.getKeyValueColumn());
				rs.close();
				pstmt.close();
				pstmt = conn.prepareStatement(this.seqTable.getUpdateSql());
				currentSequence = dbSequence + step;
				pstmt.setInt(1, currentSequence);
				pstmt.setString(2, this.getKeyType());
				pstmt.setInt(3, dbSequence);
				updateCount = pstmt.executeUpdate();
				pstmt.close();
			}
			
			conn.commit();
			conn.setAutoCommit(defaultAutoCommit);
			conn.setTransactionIsolation(defaultTranscationIoslation);
			return currentSequence;
		} catch (SQLException e) {
			LOGGER.error("get current sequence of the key error", e);
			if (conn != null) {
				try {
					conn.rollback();
				} catch (Exception f) {
					LOGGER.error("close resultset error", f);
				}

			}
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (Exception e) {
					LOGGER.error("close connection error", e);
				}
			}
		}

		throw new InvalidSequenceException("can not get sequence after try" + this.retryCount + " times");
	}

	public DatabaseSequenceTable getSeqTable() {
		return seqTable;
	}

	public void setSeqTable(DatabaseSequenceTable seqTable) {
		this.seqTable = seqTable;
	}

	public DataSource getDataSource() {
		return dataSource;
	}

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	public int getRetryCount() {
		return retryCount;
	}

	public void setRetryCount(int retryCount) {
		this.retryCount = retryCount;
	}
}
